package org.sa;

import java.util.ArrayList;
import java.util.ListIterator;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;

public class BeatModel implements BeatModelInterface, MetaEventListener {
	
	Sequencer sequencer ; 
	
	Sequence sequence ; 
	Track track ; 
	
	ArrayList<BeatObserver> beatObservers = new ArrayList<BeatObserver>() ;
	ArrayList<BPMObserver> bpmObservers = new ArrayList<BPMObserver>();

	private int bpm; 

	@Override
	public void initialize() {
		setUpMidi(); 
		buildTrackAndStart(); 		
	}

	private void buildTrackAndStart()  {
		// TODO Auto-generated method stub
		int [] trackList = { 35, 0, 46, 0 } ; 
		
		sequence.deleteTrack(null);
		track = sequence.createTrack(); 
		
		// Make Track for me
		for ( int xKey = 0 ; xKey < trackList.length ; xKey++) {
			int key = trackList[xKey] ; 
			if ( key != 0 ) {
				track.add( makeEvent(144,9,key,100,xKey)); 
				track.add( makeEvent(128,9,key,100,xKey+1));
			}
		}
		
		track.add(makeEvent(192,9,1,0,4));
		try {
			sequencer.setSequence(sequence);
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	private MidiEvent makeEvent(int comd, int channel, int one, int two, int tick) {
		MidiEvent event = null ; 
	
		try {
			ShortMessage msg = new ShortMessage();
			msg.setMessage(comd, channel, one, two);
			event = new MidiEvent(msg, tick);
			
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return event;
	}

	private void setUpMidi() {
		// TODO Auto-generated method stub
		try {
			sequencer = MidiSystem.getSequencer();

			sequencer.open();
			sequencer.addMetaEventListener(this);
			sequence = new Sequence(Sequence.PPQ, 4); 
			track = sequence.createTrack(); 
			sequencer.setTempoInBPM(getBPM());
		} catch (MidiUnavailableException | InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}

	@Override
	public void on() {
		sequencer.start();		
		setBPM(90); 		
	}

	@Override
	public void off() {
		setBPM(0);
		sequencer.stop(); 		
	}

	@Override
	public void setBPM(int bpm) {
		this.bpm = bpm ;
		
		sequencer.setTempoInBPM(getBPM());
		sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);  
		notifyObservers(); 
		
	}

	private void notifyObservers() {
		 for (BPMObserver bpmO : this.bpmObservers )
		        bpmO.updateBPM()  ;
		 for (BeatObserver beatO : this.beatObservers )
			 beatO.updateBeat(); 
		 
	}

	@Override
	public int getBPM() {
		// TODO Auto-generated method stub
		return bpm;
	}

	@Override
	public void registerObserver(BeatObserver observer) {
		this.beatObservers.add(observer);		
	}
	
	@Override
	public void registerObserver(BPMObserver observer) {
		this.bpmObservers.add(observer);
	}

	
	@Override
	public void removeObserver(BeatObserver observer) {
		this.beatObservers.remove(observer);		
	}
	@Override
	public void removeObserver(BPMObserver observer) {
		this.bpmObservers.remove(observer);		
	}

	@Override
	public void meta(MetaMessage arg0) {
		// TODO Auto-generated method stub
		System.out.println("Meta Event" + arg0.getType() );			
	}

}
